package org.reborn.engine.model.mesh

import scala.collection.mutable.ListBuffer

import _root_.org.reborn.math.util.MathUtils
import _root_.org.reborn.math._
import _root_.org.reborn.engine.enums.ArrayType._
import _root_.org.reborn.engine.enums.FaceMode._
import _root_.org.reborn.utils.ListUtil

import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.HashMap

import java.nio.FloatBuffer
import java.nio.IntBuffer


object MeshUtil {

  /**
   * Create a mutable mesh with specified face mode and initial room for specified # of faces.
   */
  def MutableMesh(fMode:FaceMode,initialFaceCount:Int,resizeAmount:Int):MutableMesh = {
    new MutableMesh(fMode,initialFaceCount,resizeAmount)
  }
  /**
   * Create a mutable mesh with specified face mode and initial room for specified # of faces. Default resize amount is 100 faces.
   */
  def MutableMesh(fMode:FaceMode,initialFaceCount:Int):MutableMesh = {
    MutableMesh(fMode,initialFaceCount,100)
  }
  /**
   * Create a mutable with specified face mode. Initial face count and resize amount are both 100.
   */
  def MutableMesh(fMode:FaceMode):MutableMesh = {
    MutableMesh(fMode,100)
  }
  /**
   * Create a mutable mesh from another Mesh. New mesh will contain same number of faces as original
   * mesh, with a resize amount of 100 faces.
   */
  def MutableMesh(mesh:Mesh):MutableMesh = {
    mesh match{
      case m:MutableMesh => mesh.asInstanceOf[MutableMesh]
      case _ => MutableMesh(mesh.getFaceMode,mesh.faces).append(mesh)
    }
  }
  
  /**
   * Create a mutable mesh from a MeshConnectivity object.
   */
  def MutableMesh(meshCon:MeshConnectivity):MutableMesh = {
	MeshConnectivityUtil.MutableMesh(meshCon)
  }
  
  /**
   * Create a mesh that can render the vertex normals of the given mesh
   * @param mesh A Mesh with vertex normals
   * @return a new MutableMesh which when rendered will display the normals
   * of the original mesh as a lines 
   */
  def NormalMesh(mesh:Mesh,normalLength:Float):MutableMesh = {
	val normMesh = MutableMesh(Line,mesh.faces)
	
	val vertArray = mesh(Vertex).getDataArray
	val normArray = mesh(Normal).getDataArray
	
	val normVertArray = new Array[Float](vertArray.size * 2)
	val faceArray = new Array[Int](vertArray.size/3)
	
	for(i <- 0 until (vertArray.size/3) ){
	  val ind = i * 3
	  normVertArray(ind*2) = vertArray(ind)
	  normVertArray(ind*2+1) = vertArray(ind+1)
	  normVertArray(ind*2+2) = vertArray(ind+2)
	  
	  normVertArray(ind*2+3) = vertArray(ind) + normArray(ind)*normalLength
	  normVertArray(ind*2+4) = vertArray(ind+1) + normArray(ind+1)*normalLength
	  normVertArray(ind*2+5) = vertArray(ind+2) + normArray(ind+2)*normalLength
	}
	
	for(i <- 0 until faceArray.size){
	  faceArray(i)=i
	}
	
	normMesh.replaceFaceData(faceArray)
	normMesh.replaceVertData(Vertex,3,normVertArray)
	normMesh
  }
  
  /**
   * Finds the connectivity of a mesh in terms of vertices, faces, and edges.
   * @param mesh The mesh to get connectivity data for
   * @param withVertData Specifies whether or not the connectivity will include vertex data from the mesh.
   */
  def calcMeshConnectivity(mesh:Mesh,withVertData:Boolean):MeshConnectivity = {
	 MeshConnectivityUtil.calcMeshConnectivity(mesh,withVertData)
  }
  
  /**
   * Finds the connectivity of a mesh in terms of vertices, faces, and edges.
   * @param mesh The mesh to get connectivity data for
   * @param vertDataTypes Specifies the types of vertex data to also include in the connectivity data.
   */
  def calcMeshConnectivity(mesh:Mesh,vertDataTypes:Iterable[ArrayType]):MeshConnectivity = {
	MeshConnectivityUtil.calcMeshConnectivity(mesh,vertDataTypes)
  }
  
  /**
   * Calculates normals on a per-face basis. 
   * @param meshCon The mesh connectivity to modify
   * @param angleDegrees An angle in degrees that is used to determine if a vertex that isn't
   * explicitly shared by 2 faces should take the average of the face normals.
   * @param isEquivalent a Function used to determine if 2 vertices should be considered equal
   * @return the modified MeshConnectivity object
   */
  def calculateNormals(meshCon:MeshConnectivity,angleDegrees:Float,equivVerts:Map[ConnectedVertex,Set[ConnectedVertex]]):MeshConnectivity = {
	MeshConnectivityUtil.calculateNormals(meshCon,angleDegrees,equivVerts)
  }
  
  /**
   * 
   * @param meshCon
   * @return
   */
  def expandVertices(meshCon:MeshConnectivity):MeshConnectivity = {
	  MeshConnectivityUtil.expandVertices(meshCon)
  }
  
  /**
   * Removes duplicate vertices in a MeshConnectivity object based on a user-defined set
   * of limits for each ArrayType.
   * @param meshCon The mesh connectivity to modify
   * @param isDuplicate is a Function used to determine if 2 vertices should be considered equal
   * @return the modified MeshConnectivity object
   */
  def removeDuplicateVertices(meshCon:MeshConnectivity,isDuplicate:EqualsFunc):MeshConnectivity = {
	  MeshConnectivityUtil.removeDuplicateVertices(meshCon,isDuplicate)
  }
  
  
  def reduceMesh(meshCon:MeshConnectivity,
		  edgeCostFunc:EdgeCostFunc,
		  midpointFunc:MidpointFunc,
		  conditionFunc:ConditionFunc):MeshConnectivity = {
	  MeshConnectivityUtil.reduceMesh(meshCon,edgeCostFunc,midpointFunc,conditionFunc)
  }
  
  
  /**
   * Partitions the mesh into 2 smaller meshes.  The first mesh
   * contains the polygons that are on the negative side of the 
   * partitioning plane.  The second mesh contains polygons on the
   * positive side.  
   * 
   * Polygons with vertices on both sides are split along the 
   * intersecting plane.
   * 
   * Polygons with faces parallel to the intersecting plane, and 
   * that lie on the intersecting plane will be placed in the first
   * mesh.
   */
  def partition(mesh:Mesh,plane:Plane3F,minDist:Float):(MutableMesh,MutableMesh) = {
    val m = MutableMesh(mesh)   //.expandVertices
    
    val negMesh = MutableMesh(m.getFaceMode,m.faces / 2)
    val posMesh = MutableMesh(m.getFaceMode,m.faces / 2)
    
    val faces = m.faces
    
    var i=0;
    while(i < faces){
      val (negFaces,posFaces) = m.getFaceAt(i).split(plane,minDist)
      for(face <- negFaces) { negMesh.append(face) }
      for(face <- posFaces) { posMesh.append(face) }
      
      i+=1;
    }
    
    (negMesh,posMesh)
  }
  
  type EqualsFunc = MeshConnectivityUtil.EqualsFunc
  type EdgeCostFunc = MeshConnectivityUtil.EdgeCostFunc
  type MidpointFunc = MeshConnectivityUtil.MidpointFunc
  type ConditionFunc = MeshConnectivityUtil.ConditionFunc

}

